/*!
 *
 * threeoctree.js (r60) / https://github.com/collinhover/threeoctree
 * (sparse) dynamic 3D spatial representation structure for fast searches.
 *
 * @author Collin Hover / http://collinhover.com/
 * based on Dynamic Octree by Piko3D @ http://www.piko3d.com/ and Octree by Marek Pawlowski @ pawlowski.it
 *
 */
 var THREE=require('three');
 ( function ( THREE ) {

	"use strict";


	/*===================================================

	utility

	=====================================================*/

	function isNumber ( n ) {

		return ! isNaN( n ) && isFinite( n );

	}

	function isArray ( target ) {

		return Object.prototype.toString.call( target ) === '[object Array]';

	}

	function toArray ( target ) {

		return target ? ( isArray ( target ) !== true ? [ target ] : target ) : [];

	}

	function indexOfValue( array, value ) {

		for ( var i = 0, il = array.length; i < il; i ++ ) {

			if ( array[ i ] === value ) {

				return i;

			}

		}

		return - 1;

	}

	function indexOfPropertyWithValue( array, property, value ) {

		for ( var i = 0, il = array.length; i < il; i ++ ) {

			if ( array[ i ][ property ] === value ) {

				return i;

			}

		}

		return - 1;

	}

	/*===================================================

	octree

	=====================================================*/

	THREE.Octree = function ( parameters ) {

		// handle parameters

		parameters = parameters || {};

		parameters.tree = this;

		// static properties ( modification is not recommended )

		this.nodeCount = 0;

		this.INDEX_INSIDE_CROSS = - 1;
		this.INDEX_OUTSIDE_OFFSET = 2;

		this.INDEX_OUTSIDE_POS_X = isNumber( parameters.INDEX_OUTSIDE_POS_X ) ? parameters.INDEX_OUTSIDE_POS_X : 0;
		this.INDEX_OUTSIDE_NEG_X = isNumber( parameters.INDEX_OUTSIDE_NEG_X ) ? parameters.INDEX_OUTSIDE_NEG_X : 1;
		this.INDEX_OUTSIDE_POS_Y = isNumber( parameters.INDEX_OUTSIDE_POS_Y ) ? parameters.INDEX_OUTSIDE_POS_Y : 2;
		this.INDEX_OUTSIDE_NEG_Y = isNumber( parameters.INDEX_OUTSIDE_NEG_Y ) ? parameters.INDEX_OUTSIDE_NEG_Y : 3;
		this.INDEX_OUTSIDE_POS_Z = isNumber( parameters.INDEX_OUTSIDE_POS_Z ) ? parameters.INDEX_OUTSIDE_POS_Z : 4;
		this.INDEX_OUTSIDE_NEG_Z = isNumber( parameters.INDEX_OUTSIDE_NEG_Z ) ? parameters.INDEX_OUTSIDE_NEG_Z : 5;

		this.INDEX_OUTSIDE_MAP = [];
		this.INDEX_OUTSIDE_MAP[ this.INDEX_OUTSIDE_POS_X ] = { index: this.INDEX_OUTSIDE_POS_X, count: 0, x: 1, y: 0, z: 0 };
		this.INDEX_OUTSIDE_MAP[ this.INDEX_OUTSIDE_NEG_X ] = { index: this.INDEX_OUTSIDE_NEG_X, count: 0, x: - 1, y: 0, z: 0 };
		this.INDEX_OUTSIDE_MAP[ this.INDEX_OUTSIDE_POS_Y ] = { index: this.INDEX_OUTSIDE_POS_Y, count: 0, x: 0, y: 1, z: 0 };
		this.INDEX_OUTSIDE_MAP[ this.INDEX_OUTSIDE_NEG_Y ] = { index: this.INDEX_OUTSIDE_NEG_Y, count: 0, x: 0, y: - 1, z: 0 };
		this.INDEX_OUTSIDE_MAP[ this.INDEX_OUTSIDE_POS_Z ] = { index: this.INDEX_OUTSIDE_POS_Z, count: 0, x: 0, y: 0, z: 1 };
		this.INDEX_OUTSIDE_MAP[ this.INDEX_OUTSIDE_NEG_Z ] = { index: this.INDEX_OUTSIDE_NEG_Z, count: 0, x: 0, y: 0, z: - 1 };

		this.FLAG_POS_X = 1 << ( this.INDEX_OUTSIDE_POS_X + 1 );
		this.FLAG_NEG_X = 1 << ( this.INDEX_OUTSIDE_NEG_X + 1 );
		this.FLAG_POS_Y = 1 << ( this.INDEX_OUTSIDE_POS_Y + 1 );
		this.FLAG_NEG_Y = 1 << ( this.INDEX_OUTSIDE_NEG_Y + 1 );
		this.FLAG_POS_Z = 1 << ( this.INDEX_OUTSIDE_POS_Z + 1 );
		this.FLAG_NEG_Z = 1 << ( this.INDEX_OUTSIDE_NEG_Z + 1 );

		this.utilVec31Search = new THREE.Vector3();
		this.utilVec32Search = new THREE.Vector3();

		// pass scene to see octree structure

		this.scene = parameters.scene;

		if ( this.scene ) {

			this.visualGeometry = new THREE.BoxGeometry( 1, 1, 1 );
			this.visualMaterial = new THREE.MeshBasicMaterial( { color: 0xFF0066, wireframe: true, wireframeLinewidth: 1 } );

		}

		// properties

		this.objects = [];
		this.objectsMap = {};
		this.objectsData = [];
		this.objectsDeferred = [];

		this.depthMax = isNumber( parameters.depthMax ) ? parameters.depthMax : Infinity;
		this.objectsThreshold = isNumber( parameters.objectsThreshold ) ? parameters.objectsThreshold : 8;
		this.overlapPct = isNumber( parameters.overlapPct ) ? parameters.overlapPct : 0.15;
		this.undeferred = parameters.undeferred || false;

		this.root = parameters.root instanceof THREE.OctreeNode ? parameters.root : new THREE.OctreeNode( parameters );

	};

	THREE.Octree.prototype = {

		update: function () {

			// add any deferred objects that were waiting for render cycle

			if ( this.objectsDeferred.length > 0 ) {

				for ( var i = 0, il = this.objectsDeferred.length; i < il; i ++ ) {

					var deferred = this.objectsDeferred[ i ];

					this.addDeferred( deferred.object, deferred.options );

				}

				this.objectsDeferred.length = 0;

			}

		},

		add: function ( object, options ) {

			// add immediately

			if ( this.undeferred ) {

				this.updateObject( object );

				this.addDeferred( object, options );

			} else {

				// defer add until update called

				this.objectsDeferred.push( { object: object, options: options } );

			}

		},

		addDeferred: function ( object, options ) {

			var i, l,
				geometry,
				faces,
				useFaces,
				vertices,
				useVertices,
				objectData;

			// ensure object is not object data

			if ( object instanceof THREE.OctreeObjectData ) {

				object = object.object;

			}

			// check uuid to avoid duplicates

			if ( ! object.uuid ) {

				object.uuid = THREE.Math.generateUUID();

			}

			if ( ! this.objectsMap[ object.uuid ] ) {

				// store

				this.objects.push( object );
				this.objectsMap[ object.uuid ] = object;

				// check options

				if ( options ) {

					useFaces = options.useFaces;
					useVertices = options.useVertices;

				}

				if ( useVertices === true ) {

					geometry = object.geometry;
					vertices = geometry.vertices;

					for ( i = 0, l = vertices.length; i < l; i ++ ) {

						this.addObjectData( object, vertices[ i ] );

					}

				} else if ( useFaces === true ) {

					geometry = object.geometry;
					faces = geometry.faces;

					for ( i = 0, l = faces.length; i < l; i ++ ) {

						this.addObjectData( object, faces[ i ] );

					}

				} else {

					this.addObjectData( object );

				}

			}

		},

		addObjectData: function ( object, part ) {

			var objectData = new THREE.OctreeObjectData( object, part );

			// add to tree objects data list

			this.objectsData.push( objectData );

			// add to nodes

			this.root.addObject( objectData );

		},

		remove: function ( object ) {

			var i, l,
				objectData = object,
				index,
				objectsDataRemoved;

			// ensure object is not object data for index search

			if ( object instanceof THREE.OctreeObjectData ) {

				object = object.object;

			}

			// check uuid

			if ( this.objectsMap[ object.uuid ] ) {

				this.objectsMap[ object.uuid ] = undefined;

				// check and remove from objects, nodes, and data lists

				index = indexOfValue( this.objects, object );

				if ( index !== - 1 ) {

					this.objects.splice( index, 1 );

					// remove from nodes

					objectsDataRemoved = this.root.removeObject( objectData );

					// remove from objects data list

					for ( i = 0, l = objectsDataRemoved.length; i < l; i ++ ) {

						objectData = objectsDataRemoved[ i ];

						index = indexOfValue( this.objectsData, objectData );

						if ( index !== - 1 ) {

							this.objectsData.splice( index, 1 );

						}

					}

				}

			} else if ( this.objectsDeferred.length > 0 ) {

				// check and remove from deferred

				index = indexOfPropertyWithValue( this.objectsDeferred, 'object', object );

				if ( index !== - 1 ) {

					this.objectsDeferred.splice( index, 1 );

				}

			}

		},

		extend: function ( octree ) {

			var i, l,
				objectsData,
				objectData;

			if ( octree instanceof THREE.Octree ) {

				// for each object data

				objectsData = octree.objectsData;

				for ( i = 0, l = objectsData.length; i < l; i ++ ) {

					objectData = objectsData[ i ];

					this.add( objectData, { useFaces: objectData.faces, useVertices: objectData.vertices } );

				}

			}

		},

		rebuild: function () {

			var i, l,
				node,
				object,
				objectData,
				indexOctant,
				indexOctantLast,
				objectsUpdate = [];

			// check all object data for changes in position
			// assumes all object matrices are up to date

			for ( i = 0, l = this.objectsData.length; i < l; i ++ ) {

				objectData = this.objectsData[ i ];

				node = objectData.node;

				// update object

				objectData.update();

				// if position has changed since last organization of object in tree

				if ( node instanceof THREE.OctreeNode && ! objectData.positionLast.equals( objectData.position ) ) {

					// get octant index of object within current node

					indexOctantLast = objectData.indexOctant;

					indexOctant = node.getOctantIndex( objectData );

					// if object octant index has changed

					if ( indexOctant !== indexOctantLast ) {

						// add to update list

						objectsUpdate.push( objectData );

					}

				}

			}

			// update changed objects

			for ( i = 0, l = objectsUpdate.length; i < l; i ++ ) {

				objectData = objectsUpdate[ i ];

				// remove object from current node

				objectData.node.removeObject( objectData );

				// add object to tree root

				this.root.addObject( objectData );

			}

		},

		updateObject: function ( object ) {

			var i, l,
				parentCascade = [ object ],
				parent,
				parentUpdate;

			// search all parents between object and root for world matrix update

			parent = object.parent;

			while ( parent ) {

				parentCascade.push( parent );
				parent = parent.parent;

			}

			for ( i = 0, l = parentCascade.length; i < l; i ++ ) {

				parent = parentCascade[ i ];

				if ( parent.matrixWorldNeedsUpdate === true ) {

					parentUpdate = parent;

				}

			}

			// update world matrix starting at uppermost parent that needs update

			if ( typeof parentUpdate !== 'undefined' ) {

				parentUpdate.updateMatrixWorld();

			}

		},

		search: function ( position, radius, organizeByObject, direction ) {
			var i, l,
				node,
				objects,
				objectData,
				object,
				results,
				resultData,
				resultsObjectsIndices,
				resultObjectIndex,
				directionPct;

			// add root objects

			objects = [].concat( this.root.objects );

			// ensure radius (i.e. distance of ray) is a number

			if ( ! ( radius > 0 ) ) {

				radius = Number.MAX_VALUE;

			}

			// if direction passed, normalize and find pct

			if ( direction instanceof THREE.Vector3 ) {

				direction = this.utilVec31Search.copy( direction ).normalize();
				directionPct = this.utilVec32Search.set( 1, 1, 1 ).divide( direction );

			}

			// search each node of root

			for ( i = 0, l = this.root.nodesIndices.length; i < l; i ++ ) {

				node = this.root.nodesByIndex[ this.root.nodesIndices[ i ] ];

				objects = node.search( position, radius, objects, direction, directionPct );

			}

			// if should organize results by object

			if ( organizeByObject === true ) {

				results = [];
				resultsObjectsIndices = [];

				// for each object data found

				for ( i = 0, l = objects.length; i < l; i ++ ) {

					objectData = objects[ i ];
					object = objectData.object;

					resultObjectIndex = indexOfValue( resultsObjectsIndices, object );

					// if needed, create new result data

					if ( resultObjectIndex === - 1 ) {

						resultData = {
							object: object,
							faces: [],
							vertices: []
						};

						results.push( resultData );

						resultsObjectsIndices.push( object );

					} else {

						resultData = results[ resultObjectIndex ];

					}

					// object data has faces or vertices, add to list

					if ( objectData.faces ) {

						resultData.faces.push( objectData.faces );

					} else if ( objectData.vertices ) {

						resultData.vertices.push( objectData.vertices );

					}

				}

			} else {

				results = objects;

			}

			return results;

		},

		setRoot: function ( root ) {

			if ( root instanceof THREE.OctreeNode ) {

				// store new root

				this.root = root;

				// update properties

				this.root.updateProperties();

			}

		},

		getDepthEnd: function () {

			return this.root.getDepthEnd();

		},

		getNodeCountEnd: function () {

			return this.root.getNodeCountEnd();

		},

		getObjectCountEnd: function () {

			return this.root.getObjectCountEnd();

		},

		toConsole: function () {

			this.root.toConsole();

		}

	};

	/*===================================================

	object data

	=====================================================*/

	THREE.OctreeObjectData = function ( object, part ) {

		// properties

		this.object = object;

		// handle part by type

		if ( part instanceof THREE.Face3 ) {

			this.faces = part;
			this.face3 = true;
			this.utilVec31FaceBounds = new THREE.Vector3();

		} else if ( part instanceof THREE.Vector3 ) {

			this.vertices = part;

		}

		this.radius = 0;
		this.position = new THREE.Vector3();

		// initial update

		if ( this.object instanceof THREE.Object3D ) {

			this.update();

		}

		this.positionLast = this.position.clone();

	};

	THREE.OctreeObjectData.prototype = {

		update: function () {

			if ( this.face3 ) {

				this.radius = this.getFace3BoundingRadius( this.object, this.faces );
				this.position.copy( this.faces.centroid ).applyMatrix4( this.object.matrixWorld );

			} else if ( this.vertices ) {

				this.radius = this.object.material.size || 1;
				this.position.copy( this.vertices ).applyMatrix4( this.object.matrixWorld );

			} else {

				if ( this.object.geometry ) {

					if ( this.object.geometry.boundingSphere === null ) {

						this.object.geometry.computeBoundingSphere();

					}

					this.radius = this.object.geometry.boundingSphere.radius;
					this.position.copy( this.object.geometry.boundingSphere.center ).applyMatrix4( this.object.matrixWorld );

				} else {

					this.radius = this.object.boundRadius;
					this.position.setFromMatrixPosition( this.object.matrixWorld );

				}

			}

			this.radius = this.radius * Math.max( this.object.scale.x, this.object.scale.y, this.object.scale.z );

		},

		getFace3BoundingRadius: function ( object, face ) {

			if ( face.centroid === undefined ) face.centroid = new THREE.Vector3();

			var geometry = object.geometry || object,
				vertices = geometry.vertices,
				centroid = face.centroid,
				va = vertices[ face.a ], vb = vertices[ face.b ], vc = vertices[ face.c ],
				centroidToVert = this.utilVec31FaceBounds,
				radius;

			centroid.addVectors( va, vb ).add( vc ).divideScalar( 3 );
			radius = Math.max( centroidToVert.subVectors( centroid, va ).length(), centroidToVert.subVectors( centroid, vb ).length(), centroidToVert.subVectors( centroid, vc ).length() );

			return radius;

		}

	};

	/*===================================================

	node

	=====================================================*/

	THREE.OctreeNode = function ( parameters ) {

		// utility

		this.utilVec31Branch = new THREE.Vector3();
		this.utilVec31Expand = new THREE.Vector3();
		this.utilVec31Ray = new THREE.Vector3();

		// handle parameters

		parameters = parameters || {};

		// store or create tree

		if ( parameters.tree instanceof THREE.Octree ) {

			this.tree = parameters.tree;

		} else if ( parameters.parent instanceof THREE.OctreeNode !== true ) {

			parameters.root = this;

			this.tree = new THREE.Octree( parameters );

		}

		// basic properties

		this.id = this.tree.nodeCount ++;
		this.position = parameters.position instanceof THREE.Vector3 ? parameters.position : new THREE.Vector3();
		this.radius = parameters.radius > 0 ? parameters.radius : 1;
		this.indexOctant = parameters.indexOctant;
		this.depth = 0;

		// reset and assign parent

		this.reset();
		this.setParent( parameters.parent );

		// additional properties

		this.overlap = this.radius * this.tree.overlapPct;
		this.radiusOverlap = this.radius + this.overlap;
		this.left = this.position.x - this.radiusOverlap;
		this.right = this.position.x + this.radiusOverlap;
		this.bottom = this.position.y - this.radiusOverlap;
		this.top = this.position.y + this.radiusOverlap;
		this.back = this.position.z - this.radiusOverlap;
		this.front = this.position.z + this.radiusOverlap;

		// visual

		if ( this.tree.scene ) {

			this.visual = new THREE.Mesh( this.tree.visualGeometry, this.tree.visualMaterial );
			this.visual.scale.set( this.radiusOverlap * 2, this.radiusOverlap * 2, this.radiusOverlap * 2 );
			this.visual.position.copy( this.position );
			this.tree.scene.add( this.visual );

		}

	};

	THREE.OctreeNode.prototype = {

		setParent: function ( parent ) {

			// store new parent

			if ( parent !== this && this.parent !== parent ) {

				this.parent = parent;

				// update properties

				this.updateProperties();

			}

		},

		updateProperties: function () {

			var i, l;

			// properties

			if ( this.parent instanceof THREE.OctreeNode ) {

				this.tree = this.parent.tree;
				this.depth = this.parent.depth + 1;

			} else {

				this.depth = 0;

			}

			// cascade

			for ( i = 0, l = this.nodesIndices.length; i < l; i ++ ) {

				this.nodesByIndex[ this.nodesIndices[ i ] ].updateProperties();

			}

		},

		reset: function ( cascade, removeVisual ) {

			var i, l,
				node,
				nodesIndices = this.nodesIndices || [],
				nodesByIndex = this.nodesByIndex;

			this.objects = [];
			this.nodesIndices = [];
			this.nodesByIndex = {};

			// unset parent in nodes

			for ( i = 0, l = nodesIndices.length; i < l; i ++ ) {

				node = nodesByIndex[ nodesIndices[ i ] ];

				node.setParent( undefined );

				if ( cascade === true ) {

					node.reset( cascade, removeVisual );

				}

			}

			// visual

			if ( removeVisual === true && this.visual && this.visual.parent ) {

				this.visual.parent.remove( this.visual );

			}

		},

		addNode: function ( node, indexOctant ) {

			node.indexOctant = indexOctant;

			if ( indexOfValue( this.nodesIndices, indexOctant ) === - 1 ) {

				this.nodesIndices.push( indexOctant );

			}

			this.nodesByIndex[ indexOctant ] = node;

			if ( node.parent !== this ) {

				node.setParent( this );

			}

		},

		removeNode: function ( indexOctant ) {

			var index,
				node;

			index = indexOfValue( this.nodesIndices, indexOctant );

			this.nodesIndices.splice( index, 1 );

			node = node || this.nodesByIndex[ indexOctant ];

			delete this.nodesByIndex[ indexOctant ];

			if ( node.parent === this ) {

				node.setParent( undefined );

			}

		},

		addObject: function ( object ) {

			var index,
				indexOctant,
				node;

			// get object octant index

			indexOctant = this.getOctantIndex( object );

			// if object fully contained by an octant, add to subtree
			if ( indexOctant > - 1 && this.nodesIndices.length > 0 ) {

				node = this.branch( indexOctant );

				node.addObject( object );

			} else if ( indexOctant < - 1 && this.parent instanceof THREE.OctreeNode ) {

				// if object lies outside bounds, add to parent node

				this.parent.addObject( object );

			} else {

				// add to this objects list

				index = indexOfValue( this.objects, object );

				if ( index === - 1 ) {

					this.objects.push( object );

				}

				// node reference

				object.node = this;

				// check if need to expand, split, or both

				this.checkGrow();

			}

		},

		addObjectWithoutCheck: function ( objects ) {

			var i, l,
				object;

			for ( i = 0, l = objects.length; i < l; i ++ ) {

				object = objects[ i ];

				this.objects.push( object );

				object.node = this;

			}

		},

		removeObject: function ( object ) {

			var i, l,
				nodesRemovedFrom,
				removeData;

			// cascade through tree to find and remove object

			removeData = this.removeObjectRecursive( object, { searchComplete: false, nodesRemovedFrom: [], objectsDataRemoved: [] } );

			// if object removed, try to shrink the nodes it was removed from

			nodesRemovedFrom = removeData.nodesRemovedFrom;

			if ( nodesRemovedFrom.length > 0 ) {

				for ( i = 0, l = nodesRemovedFrom.length; i < l; i ++ ) {

					nodesRemovedFrom[ i ].shrink();

				}

			}

			return removeData.objectsDataRemoved;

		},

		removeObjectRecursive: function ( object, removeData ) {

			var i, l,
				index = - 1,
				objectData,
				node,
				objectRemoved;

			// find index of object in objects list

			// search and remove object data (fast)
			if ( object instanceof THREE.OctreeObjectData ) {

				// remove from this objects list

				index = indexOfValue( this.objects, object );

				if ( index !== - 1 ) {

					this.objects.splice( index, 1 );
					object.node = undefined;

					removeData.objectsDataRemoved.push( object );

					removeData.searchComplete = objectRemoved = true;

				}

			} else {

				// search each object data for object and remove (slow)

				for ( i = this.objects.length - 1; i >= 0; i -- ) {

					objectData = this.objects[ i ];

					if ( objectData.object === object ) {

						this.objects.splice( i, 1 );
						objectData.node = undefined;

						removeData.objectsDataRemoved.push( objectData );

						objectRemoved = true;

						if ( ! objectData.faces && ! objectData.vertices ) {

							removeData.searchComplete = true;
							break;

						}

					}

				}

			}

			// if object data removed and this is not on nodes removed from

			if ( objectRemoved === true ) {

				removeData.nodesRemovedFrom.push( this );

			}

			// if search not complete, search nodes

			if ( removeData.searchComplete !== true ) {

				for ( i = 0, l = this.nodesIndices.length; i < l; i ++ ) {

					node = this.nodesByIndex[ this.nodesIndices[ i ] ];

					// try removing object from node

					removeData = node.removeObjectRecursive( object, removeData );

					if ( removeData.searchComplete === true ) {

						break;

					}

				}

			}

			return removeData;

		},

		checkGrow: function () {

			// if object count above max

			if ( this.objects.length > this.tree.objectsThreshold && this.tree.objectsThreshold > 0 ) {

				this.grow();

			}

		},

		grow: function () {

			var indexOctant,
				object,
				objectsExpand = [],
				objectsExpandOctants = [],
				objectsSplit = [],
				objectsSplitOctants = [],
				objectsRemaining = [],
				i, l;

			// for each object

			for ( i = 0, l = this.objects.length; i < l; i ++ ) {

				object = this.objects[ i ];

				// get object octant index

				indexOctant = this.getOctantIndex( object );

				// if lies within octant
				if ( indexOctant > - 1 ) {

					objectsSplit.push( object );
					objectsSplitOctants.push( indexOctant );

				} else if ( indexOctant < - 1 ) {

					// lies outside radius

					objectsExpand.push( object );
					objectsExpandOctants.push( indexOctant );

				} else {

					// lies across bounds between octants

					objectsRemaining.push( object );

				}

			}

			// if has objects to split

			if ( objectsSplit.length > 0 ) {

				objectsRemaining = objectsRemaining.concat( this.split( objectsSplit, objectsSplitOctants ) );

			}

			// if has objects to expand

			if ( objectsExpand.length > 0 ) {

				objectsRemaining = objectsRemaining.concat( this.expand( objectsExpand, objectsExpandOctants ) );

			}

			// store remaining

			this.objects = objectsRemaining;

			// merge check

			this.checkMerge();

		},

		split: function ( objects, octants ) {

			var i, l,
				indexOctant,
				object,
				node,
				objectsRemaining;

			// if not at max depth

			if ( this.depth < this.tree.depthMax ) {

				objects = objects || this.objects;

				octants = octants || [];

				objectsRemaining = [];

				// for each object

				for ( i = 0, l = objects.length; i < l; i ++ ) {

					object = objects[ i ];

					// get object octant index

					indexOctant = octants[ i ];

					// if object contained by octant, branch this tree

					if ( indexOctant > - 1 ) {

						node = this.branch( indexOctant );

						node.addObject( object );

					} else {

						objectsRemaining.push( object );

					}

				}

				// if all objects, set remaining as new objects

				if ( objects === this.objects ) {

					this.objects = objectsRemaining;

				}

			} else {

				objectsRemaining = this.objects;

			}

			return objectsRemaining;

		},

		branch: function ( indexOctant ) {

			var node,
				overlap,
				radius,
				radiusOffset,
				offset,
				position;

			// node exists

			if ( this.nodesByIndex[ indexOctant ] instanceof THREE.OctreeNode ) {

				node = this.nodesByIndex[ indexOctant ];

			} else {

				// properties

				radius = ( this.radiusOverlap ) * 0.5;
				overlap = radius * this.tree.overlapPct;
				radiusOffset = radius - overlap;
				offset = this.utilVec31Branch.set( indexOctant & 1 ? radiusOffset : - radiusOffset, indexOctant & 2 ? radiusOffset : - radiusOffset, indexOctant & 4 ? radiusOffset : - radiusOffset );
				position = new THREE.Vector3().addVectors( this.position, offset );

				// node

				node = new THREE.OctreeNode( {
					tree: this.tree,
					parent: this,
					position: position,
					radius: radius,
					indexOctant: indexOctant
				} );

				// store

				this.addNode( node, indexOctant );

			}

			return node;

		},

		expand: function ( objects, octants ) {

			var i, l,
				object,
				objectsRemaining,
				objectsExpand,
				indexOctant,
				flagsOutside,
				indexOutside,
				indexOctantInverse,
				iom = this.tree.INDEX_OUTSIDE_MAP,
				indexOutsideCounts,
				infoIndexOutside1,
				infoIndexOutside2,
				infoIndexOutside3,
				indexOutsideBitwise1,
				indexOutsideBitwise2,
				infoPotential1,
				infoPotential2,
				infoPotential3,
				indexPotentialBitwise1,
				indexPotentialBitwise2,
				octantX, octantY, octantZ,
				overlap,
				radius,
				radiusOffset,
				radiusParent,
				overlapParent,
				offset = this.utilVec31Expand,
				position,
				parent;

			// handle max depth down tree

			if ( this.tree.root.getDepthEnd() < this.tree.depthMax ) {

				objects = objects || this.objects;
				octants = octants || [];

				objectsRemaining = [];
				objectsExpand = [];

				// reset counts

				for ( i = 0, l = iom.length; i < l; i ++ ) {

					iom[ i ].count = 0;

				}

				// for all outside objects, find outside octants containing most objects

				for ( i = 0, l = objects.length; i < l; i ++ ) {

					object = objects[ i ];

					// get object octant index

					indexOctant = octants[ i ] ;

					// if object outside this, include in calculations

					if ( indexOctant < - 1 ) {

						// convert octant index to outside flags

						flagsOutside = - indexOctant - this.tree.INDEX_OUTSIDE_OFFSET;

						// check against bitwise flags

						// x

						if ( flagsOutside & this.tree.FLAG_POS_X ) {

							iom[ this.tree.INDEX_OUTSIDE_POS_X ].count ++;

						} else if ( flagsOutside & this.tree.FLAG_NEG_X ) {

							iom[ this.tree.INDEX_OUTSIDE_NEG_X ].count ++;

						}

						// y

						if ( flagsOutside & this.tree.FLAG_POS_Y ) {

							iom[ this.tree.INDEX_OUTSIDE_POS_Y ].count ++;

						} else if ( flagsOutside & this.tree.FLAG_NEG_Y ) {

							iom[ this.tree.INDEX_OUTSIDE_NEG_Y ].count ++;

						}

						// z

						if ( flagsOutside & this.tree.FLAG_POS_Z ) {

							iom[ this.tree.INDEX_OUTSIDE_POS_Z ].count ++;

						} else if ( flagsOutside & this.tree.FLAG_NEG_Z ) {

							iom[ this.tree.INDEX_OUTSIDE_NEG_Z ].count ++;

						}

						// store in expand list

						objectsExpand.push( object );

					} else {

						objectsRemaining.push( object );

					}

				}

				// if objects to expand

				if ( objectsExpand.length > 0 ) {

					// shallow copy index outside map

					indexOutsideCounts = iom.slice( 0 );

					// sort outside index count so highest is first

					indexOutsideCounts.sort( function ( a, b ) {

						return b.count - a.count;

					} );

					// get highest outside indices

					// first is first
					infoIndexOutside1 = indexOutsideCounts[ 0 ];
					indexOutsideBitwise1 = infoIndexOutside1.index | 1;

					// second is ( one of next two bitwise OR 1 ) that is not opposite of ( first bitwise OR 1 )

					infoPotential1 = indexOutsideCounts[ 1 ];
					infoPotential2 = indexOutsideCounts[ 2 ];

					infoIndexOutside2 = ( infoPotential1.index | 1 ) !== indexOutsideBitwise1 ? infoPotential1 : infoPotential2;
					indexOutsideBitwise2 = infoIndexOutside2.index | 1;

					// third is ( one of next three bitwise OR 1 ) that is not opposite of ( first or second bitwise OR 1 )

					infoPotential1 = indexOutsideCounts[ 2 ];
					infoPotential2 = indexOutsideCounts[ 3 ];
					infoPotential3 = indexOutsideCounts[ 4 ];

					indexPotentialBitwise1 = infoPotential1.index | 1;
					indexPotentialBitwise2 = infoPotential2.index | 1;

					infoIndexOutside3 = indexPotentialBitwise1 !== indexOutsideBitwise1 && indexPotentialBitwise1 !== indexOutsideBitwise2 ? infoPotential1 : indexPotentialBitwise2 !== indexOutsideBitwise1 && indexPotentialBitwise2 !== indexOutsideBitwise2 ? infoPotential2 : infoPotential3;

					// get this octant normal based on outside octant indices

					octantX = infoIndexOutside1.x + infoIndexOutside2.x + infoIndexOutside3.x;
					octantY = infoIndexOutside1.y + infoIndexOutside2.y + infoIndexOutside3.y;
					octantZ = infoIndexOutside1.z + infoIndexOutside2.z + infoIndexOutside3.z;

					// get this octant indices based on octant normal

					indexOctant = this.getOctantIndexFromPosition( octantX, octantY, octantZ );
					indexOctantInverse = this.getOctantIndexFromPosition( - octantX, - octantY, - octantZ );

					// properties

					overlap = this.overlap;
					radius = this.radius;

					// radius of parent comes from reversing overlap of this, unless overlap percent is 0

					radiusParent = this.tree.overlapPct > 0 ? overlap / ( ( 0.5 * this.tree.overlapPct ) * ( 1 + this.tree.overlapPct ) ) : radius * 2;
					overlapParent = radiusParent * this.tree.overlapPct;

					// parent offset is difference between radius + overlap of parent and child

					radiusOffset = ( radiusParent + overlapParent ) - ( radius + overlap );
					offset.set( indexOctant & 1 ? radiusOffset : - radiusOffset, indexOctant & 2 ? radiusOffset : - radiusOffset, indexOctant & 4 ? radiusOffset : - radiusOffset );
					position = new THREE.Vector3().addVectors( this.position, offset );

					// parent

					parent = new THREE.OctreeNode( {
						tree: this.tree,
						position: position,
						radius: radiusParent
					} );

					// set self as node of parent

					parent.addNode( this, indexOctantInverse );

					// set parent as root

					this.tree.setRoot( parent );

					// add all expand objects to parent

					for ( i = 0, l = objectsExpand.length; i < l; i ++ ) {

						this.tree.root.addObject( objectsExpand[ i ] );

					}

				}

				// if all objects, set remaining as new objects

				if ( objects === this.objects ) {

					this.objects = objectsRemaining;

				}

			} else {

				objectsRemaining = objects;

			}

			return objectsRemaining;

		},

		shrink: function () {

			// merge check

			this.checkMerge();

			// contract check

			this.tree.root.checkContract();

		},

		checkMerge: function () {

			var nodeParent = this,
				nodeMerge;

			// traverse up tree as long as node + entire subtree's object count is under minimum

			while ( nodeParent.parent instanceof THREE.OctreeNode && nodeParent.getObjectCountEnd() < this.tree.objectsThreshold ) {

				nodeMerge = nodeParent;
				nodeParent = nodeParent.parent;

			}

			// if parent node is not this, merge entire subtree into merge node

			if ( nodeParent !== this ) {

				nodeParent.merge( nodeMerge );

			}

		},

		merge: function ( nodes ) {

			var i, l,
				j, k,
				node;

			// handle nodes

			nodes = toArray( nodes );

			for ( i = 0, l = nodes.length; i < l; i ++ ) {

				node = nodes[ i ];

				// gather node + all subtree objects

				this.addObjectWithoutCheck( node.getObjectsEnd() );

				// reset node + entire subtree

				node.reset( true, true );

				// remove node

				this.removeNode( node.indexOctant, node );

			}

			// merge check

			this.checkMerge();

		},

		checkContract: function () {

			var i, l,
				node,
				nodeObjectsCount,
				nodeHeaviest,
				nodeHeaviestObjectsCount,
				outsideHeaviestObjectsCount;

			// find node with highest object count

			if ( this.nodesIndices.length > 0 ) {

				nodeHeaviestObjectsCount = 0;
				outsideHeaviestObjectsCount = this.objects.length;

				for ( i = 0, l = this.nodesIndices.length; i < l; i ++ ) {

					node = this.nodesByIndex[ this.nodesIndices[ i ] ];

					nodeObjectsCount = node.getObjectCountEnd();
					outsideHeaviestObjectsCount += nodeObjectsCount;

					if ( nodeHeaviest instanceof THREE.OctreeNode === false || nodeObjectsCount > nodeHeaviestObjectsCount ) {

						nodeHeaviest = node;
						nodeHeaviestObjectsCount = nodeObjectsCount;

					}

				}

				// subtract heaviest count from outside count

				outsideHeaviestObjectsCount -= nodeHeaviestObjectsCount;

				// if should contract

				if ( outsideHeaviestObjectsCount < this.tree.objectsThreshold && nodeHeaviest instanceof THREE.OctreeNode ) {

					this.contract( nodeHeaviest );

				}

			}

		},

		contract: function ( nodeRoot ) {

			var i, l,
				node;

			// handle all nodes

			for ( i = 0, l = this.nodesIndices.length; i < l; i ++ ) {

				node = this.nodesByIndex[ this.nodesIndices[ i ] ];

				// if node is not new root

				if ( node !== nodeRoot ) {

					// add node + all subtree objects to root

					nodeRoot.addObjectWithoutCheck( node.getObjectsEnd() );

					// reset node + entire subtree

					node.reset( true, true );

				}

			}

			// add own objects to root

			nodeRoot.addObjectWithoutCheck( this.objects );

			// reset self

			this.reset( false, true );

			// set new root

			this.tree.setRoot( nodeRoot );

			// contract check on new root

			nodeRoot.checkContract();

		},

		getOctantIndex: function ( objectData ) {

			var i, l,
				positionObj,
				radiusObj,
				position = this.position,
				radiusOverlap = this.radiusOverlap,
				overlap = this.overlap,
				deltaX, deltaY, deltaZ,
				distX, distY, distZ,
				distance,
				indexOctant = 0;

			// handle type

			if ( objectData instanceof THREE.OctreeObjectData ) {

				radiusObj = objectData.radius;

				positionObj = objectData.position;

				// update object data position last

				objectData.positionLast.copy( positionObj );

			} else if ( objectData instanceof THREE.OctreeNode ) {

				positionObj = objectData.position;

				radiusObj = 0;

			}

			// find delta and distance

			deltaX = positionObj.x - position.x;
			deltaY = positionObj.y - position.y;
			deltaZ = positionObj.z - position.z;

			distX = Math.abs( deltaX );
			distY = Math.abs( deltaY );
			distZ = Math.abs( deltaZ );
			distance = Math.max( distX, distY, distZ );

			// if outside, use bitwise flags to indicate on which sides object is outside of

			if ( distance + radiusObj > radiusOverlap ) {

				// x

				if ( distX + radiusObj > radiusOverlap ) {

					indexOctant = indexOctant ^ ( deltaX > 0 ? this.tree.FLAG_POS_X : this.tree.FLAG_NEG_X );

				}

				// y

				if ( distY + radiusObj > radiusOverlap ) {

					indexOctant = indexOctant ^ ( deltaY > 0 ? this.tree.FLAG_POS_Y : this.tree.FLAG_NEG_Y );

				}

				// z

				if ( distZ + radiusObj > radiusOverlap ) {

					indexOctant = indexOctant ^ ( deltaZ > 0 ? this.tree.FLAG_POS_Z : this.tree.FLAG_NEG_Z );

				}

				objectData.indexOctant = - indexOctant - this.tree.INDEX_OUTSIDE_OFFSET;

				return objectData.indexOctant;

			}

			// return octant index from delta xyz

			if ( deltaX - radiusObj > - overlap ) {

				// x right

				indexOctant = indexOctant | 1;

			} else if ( ! ( deltaX + radiusObj < overlap ) ) {

				// x left

				objectData.indexOctant = this.tree.INDEX_INSIDE_CROSS;
				return objectData.indexOctant;

			}

			if ( deltaY - radiusObj > - overlap ) {

				// y right

				indexOctant = indexOctant | 2;

			} else if ( ! ( deltaY + radiusObj < overlap ) ) {

				// y left

				objectData.indexOctant = this.tree.INDEX_INSIDE_CROSS;
				return objectData.indexOctant;

			}


			if ( deltaZ - radiusObj > - overlap ) {

				// z right

				indexOctant = indexOctant | 4;

			} else if ( ! ( deltaZ + radiusObj < overlap ) ) {

				// z left

				objectData.indexOctant = this.tree.INDEX_INSIDE_CROSS;
				return objectData.indexOctant;

			}

			objectData.indexOctant = indexOctant;
			return objectData.indexOctant;

		},

		getOctantIndexFromPosition: function ( x, y, z ) {

			var indexOctant = 0;

			if ( x > 0 ) {

				indexOctant = indexOctant | 1;

			}

			if ( y > 0 ) {

				indexOctant = indexOctant | 2;

			}

			if ( z > 0 ) {

				indexOctant = indexOctant | 4;

			}

			return indexOctant;

		},

		search: function ( position, radius, objects, direction, directionPct ) {

			var i, l,
				node,
				intersects;

			// test intersects by parameters

			if ( direction ) {

				intersects = this.intersectRay( position, direction, radius, directionPct );

			} else {

				intersects = this.intersectSphere( position, radius );

			}

			// if intersects

			if ( intersects === true ) {

				// gather objects

				objects = objects.concat( this.objects );

				// search subtree

				for ( i = 0, l = this.nodesIndices.length; i < l; i ++ ) {

					node = this.nodesByIndex[ this.nodesIndices[ i ] ];

					objects = node.search( position, radius, objects, direction );

				}

			}

			return objects;

		},

		intersectSphere: function ( position, radius ) {

			var	distance = radius * radius,
				px = position.x,
				py = position.y,
				pz = position.z;

			if ( px < this.left ) {

				distance -= Math.pow( px - this.left, 2 );

			} else if ( px > this.right ) {

				distance -= Math.pow( px - this.right, 2 );

			}

			if ( py < this.bottom ) {

				distance -= Math.pow( py - this.bottom, 2 );

			} else if ( py > this.top ) {

				distance -= Math.pow( py - this.top, 2 );

			}

			if ( pz < this.back ) {

				distance -= Math.pow( pz - this.back, 2 );

			} else if ( pz > this.front ) {

				distance -= Math.pow( pz - this.front, 2 );

			}

			return distance >= 0;

		},

		intersectRay: function ( origin, direction, distance, directionPct ) {

			if ( typeof directionPct === 'undefined' ) {

				directionPct = this.utilVec31Ray.set( 1, 1, 1 ).divide( direction );

			}

			var t1 = ( this.left - origin.x ) * directionPct.x,
				t2 = ( this.right - origin.x ) * directionPct.x,
				t3 = ( this.bottom - origin.y ) * directionPct.y,
				t4 = ( this.top - origin.y ) * directionPct.y,
				t5 = ( this.back - origin.z ) * directionPct.z,
				t6 = ( this.front - origin.z ) * directionPct.z,
				tmax = Math.min( Math.min( Math.max( t1, t2 ), Math.max( t3, t4 ) ), Math.max( t5, t6 ) ),
				tmin;

			// ray would intersect in reverse direction, i.e. this is behind ray
			if ( tmax < 0 ) {

				return false;

			}

			tmin = Math.max( Math.max( Math.min( t1, t2 ), Math.min( t3, t4 ) ), Math.min( t5, t6 ) );

			// if tmin > tmax or tmin > ray distance, ray doesn't intersect AABB
			if ( tmin > tmax || tmin > distance ) {

				return false;

			}

			return true;

		},

		getDepthEnd: function ( depth ) {

			var i, l,
				node;

			if ( this.nodesIndices.length > 0 ) {

				for ( i = 0, l = this.nodesIndices.length; i < l; i ++ ) {

					node = this.nodesByIndex[ this.nodesIndices[ i ] ];

					depth = node.getDepthEnd( depth );

				}

			} else {

				depth = ! depth || this.depth > depth ? this.depth : depth;

			}

			return depth;

		},

		getNodeCountEnd: function () {

			return this.tree.root.getNodeCountRecursive() + 1;

		},

		getNodeCountRecursive: function () {

			var i, l,
				count = this.nodesIndices.length;

			for ( i = 0, l = this.nodesIndices.length; i < l; i ++ ) {

				count += this.nodesByIndex[ this.nodesIndices[ i ] ].getNodeCountRecursive();

			}

			return count;

		},

		getObjectsEnd: function ( objects ) {

			var i, l,
				node;

			objects = ( objects || [] ).concat( this.objects );

			for ( i = 0, l = this.nodesIndices.length; i < l; i ++ ) {

				node = this.nodesByIndex[ this.nodesIndices[ i ] ];

				objects = node.getObjectsEnd( objects );

			}

			return objects;

		},

		getObjectCountEnd: function () {

			var i, l,
				count = this.objects.length;

			for ( i = 0, l = this.nodesIndices.length; i < l; i ++ ) {

				count += this.nodesByIndex[ this.nodesIndices[ i ] ].getObjectCountEnd();

			}

			return count;

		},

		getObjectCountStart: function () {

			var count = this.objects.length,
				parent = this.parent;

			while ( parent instanceof THREE.OctreeNode ) {

				count += parent.objects.length;
				parent = parent.parent;

			}

			return count;

		},

		toConsole: function ( space ) {

			var i, l,
				node,
				spaceAddition = '   ';

			space = typeof space === 'string' ? space : spaceAddition;

			console.log( ( this.parent ? space + ' octree NODE > ' : ' octree ROOT > ' ), this, ' // id: ', this.id, ' // indexOctant: ', this.indexOctant, ' // position: ', this.position.x, this.position.y, this.position.z, ' // radius: ', this.radius, ' // depth: ', this.depth );
			console.log( ( this.parent ? space + ' ' : ' ' ), '+ objects ( ', this.objects.length, ' ) ', this.objects );
			console.log( ( this.parent ? space + ' ' : ' ' ), '+ children ( ', this.nodesIndices.length, ' )', this.nodesIndices, this.nodesByIndex );

			for ( i = 0, l = this.nodesIndices.length; i < l; i ++ ) {

				node = this.nodesByIndex[ this.nodesIndices[ i ] ];

				node.toConsole( space + spaceAddition );

			}

		}

	};

	/*===================================================

	raycaster additional functionality

	=====================================================*/

	THREE.Raycaster.prototype.intersectOctreeObject = function ( object, recursive ) {

		var intersects,
			octreeObject,
			facesAll,
			facesSearch;

		if ( object.object instanceof THREE.Object3D ) {

			octreeObject = object;
			object = octreeObject.object;

			// temporarily replace object geometry's faces with octree object faces

			facesSearch = octreeObject.faces;
			facesAll = object.geometry.faces;

			if ( facesSearch.length > 0 ) {

				object.geometry.faces = facesSearch;


			}

			// intersect

			intersects = this.intersectObject( object, recursive );

			// revert object geometry's faces

			if ( facesSearch.length > 0 ) {

				object.geometry.faces = facesAll;

			}

		} else {

			intersects = this.intersectObject( object, recursive );

		}

		return intersects;

	};

	THREE.Raycaster.prototype.intersectOctreeObjects = function ( objects, recursive ) {

		var i, il,
			intersects = [];

		for ( i = 0, il = objects.length; i < il; i ++ ) {

			intersects = intersects.concat( this.intersectOctreeObject( objects[ i ], recursive ) );

		}

		return intersects;

	};

}( THREE ) );
let Octree=THREE.Octree;
export {Octree}
